package com.capitalbio.smd.base;

import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.regex.Matcher;

/**
 * Variation type
 * 
 * @author Wang Lei
 * 
 */
public class VariationLocation implements Comparable<VariationLocation> {

	protected Log log = LogFactory.getLog(this.getClass());

	private VariationLocationType type;
	private String start;
	private String stop;
	private String locationStr;

	public VariationLocation(VariationLocationType type, String start,
			String stop) {
		this.type = type;
		this.start = start;
		this.stop = stop;
	}

	/**
	 * create a type C variation location instance
	 * 
	 * @param start
	 * @param stop
	 * @param cdsStart
	 * @param cdsStop
	 */
	public VariationLocation(int start, int stop, List<int[]> cdsRegions) {
		this.type = VariationLocationType.C;
		this.start = getLocationStr(start, cdsRegions);
		this.stop = getLocationStr(stop, cdsRegions);
	}

	private String getLocationStr(int location, List<int[]> cdsRegions) {
		int min = cdsRegions.get(0)[0];
		int max = cdsRegions.get(cdsRegions.size() - 1)[1];
		
		String locationStr = "?";
		if (location < min) {
			locationStr = String.valueOf(location - min);
		} else if (location > max) {
			locationStr = String.valueOf("*" + (location - max));
		} else {
			int intervalSum = 0;	//sum interval length before current block
			for (int i = 0; i < cdsRegions.size(); i++) {
				int blockMin = cdsRegions.get(i)[0];
				int blockMax = cdsRegions.get(i)[1];
				
					
				if (location >= blockMin & location <= blockMax) {
					locationStr = String.valueOf(location - blockMin + 1);
				}
				else if (i < cdsRegions.size() - 1 ) {
					int nextBlockMin = cdsRegions.get(i + 1)[0];
					int interval = nextBlockMin - blockMax - 1;
					int halfInterval = (nextBlockMin + blockMax) / 2;
					
					if (location > blockMax && location <= halfInterval) {
						locationStr = String.valueOf((blockMax - intervalSum) + "+" + (location - blockMax));
					}
					else if (location > halfInterval && location < nextBlockMin) {
						locationStr = String.valueOf((nextBlockMin - intervalSum - interval) + "-" + (nextBlockMin - location));
					}
					//log.debug("blockMin:" + blockMin + " blockMax:" + blockMax + " nextBlockMin:" + nextBlockMin);
					//log.debug(intervalSum + "/" + interval + "/" + blockMax + "/" + halfInterval + "/" + nextBlockMin + "/" + location + "/" + locationStr);
					intervalSum = intervalSum + interval;
				}
			}
		}
		return locationStr;
	}

	public static VariationLocation parseVariationLocation(
			String variationLocationStr) {

		String[] elements1 = variationLocationStr.split("\\.");
		if (elements1.length == 2) {
			VariationLocationType type = VariationLocationType
					.parseVariationLocationType(elements1[0]);
			String[] elements2 = elements1[1].split("_");

			String start = elements2[0];
			String stop = start;

			if (elements2.length == 2) {
				stop = elements2[1];
			}

			return new VariationLocation(type, start, stop);
		}

		throw new IllegalArgumentException(
				"Not a llegal variation location string.");
	}

	/**
	 * tell if two variations is ajacent
	 * //TODO need review
	 * @param variation
	 * @return true / false
	 */
	public boolean isAdjacent(VariationLocation variationLocation) {
		if (type.equals(variationLocation.getType())
				&& (isAdjacent(stop, variationLocation.getStart()) || isAdjacent(
						variationLocation.getStop(), start))) {
			return true;
		}
		return false;
	}

	//TODO need review
	private boolean isAdjacent(String stop, String start) {

		if ((stop.startsWith("-") && start.startsWith("-"))
				|| (stop.startsWith("*") && start.startsWith("*"))) {
			if (Integer.parseInt(start.substring(1))
					- Integer.parseInt(stop.substring(1)) == 1) {
				return true;
			}
		} else if (!stop.startsWith("-") && !start.startsWith("-")) {
			if (Integer.parseInt(start) - Integer.parseInt(stop) == 1) {
				return true;
			}
		} else if (stop.equals("-1") && start.equals("1")) {
			return true;
		}
		//TODO 事实上, stop为-1且start为1, 或start为*1且stop为CDS的末尾时, 也算相邻, 但这里无需考虑

		return false;
	}

	public VariationLocation join(VariationLocation variationLocation) {
		log.debug("Joining " + toString() + variationLocation.toString());

		if (isAdjacent(stop, variationLocation.getStart())) {
			return new VariationLocation(type, start, variationLocation.getStop());
		} else if (isAdjacent(variationLocation.getStop(), start)) {
			return new VariationLocation(type, variationLocation.getStart(), stop);
		}
		
		return null;
	}

	/**
	 * compare the type, start and stop value between two VariationLocaion
	 * instances and return if they are same
	 * 
	 * @param object
	 * @return if two instances are same
	 */
	public boolean equals(Object object) {
		if (object == this) {
			return true;
		}

		if (object instanceof VariationLocation) {
			VariationLocation variationLocation = (VariationLocation) object;
			if (variationLocation.getType().equals(this.type)
					&& variationLocation.getStart() == this.start
					&& variationLocation.getStop() == this.stop) {
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}

	}

	/**
	 * the variation location string
	 * 
	 * @return the variation location string
	 */
	public String toString() {
		if (locationStr == null) {
			locationStr = type.toString() + "."
					+ (start.equals(stop) ? start : start + "_" + stop);
		}
		return locationStr;
	}

	@Override
	public int compareTo(VariationLocation variationLocation) {
		if (this == variationLocation) {
			return 0;
		}

		String start1 = this.start;
		String start2 = variationLocation.getStart();

		Pattern pattern = Pattern.compile("([-|*])?(\\d+)([-|+])?(\\d+)?");
		Matcher matcher1 = pattern.matcher(start1);
		matcher1.matches();
		String a1 = matcher1.group(1) == null ? "" : matcher1.group(1);
		Integer a2 = Integer.parseInt(matcher1.group(2));
		String a3 = matcher1.group(3) == null ? "" : matcher1.group(3);
		Integer a4 = matcher1.group(4) == null ? 0 : Integer.parseInt(matcher1.group(4));
		
		Matcher matcher2 = pattern.matcher(start2);
		matcher2.matches();
		String b1 = matcher2.group(1) == null ? "" : matcher2.group(1);
		Integer b2 = Integer.parseInt(matcher2.group(2));
		String b3 = matcher2.group(3) == null ? "" : matcher2.group(3);
		Integer b4 = matcher2.group(4) == null ? 0 : Integer.parseInt(matcher2.group(4));
		
		int result = 0;
		if (a1.equals(b1)) {
			if (a2.equals(b2)) {
				if (a3.equals(b3)) {
					result = a4.compareTo(b4);
				}
				else if (a3.equals("-")) {
					result = -1;
				}
				else {
					result = 1;
				}
			}
			else {
				result = a2.compareTo(b2);
			}
		}
		else if (a1.equals("-")) {
			result = -1;
		}
		else if (a1.equals("+")) {
			result = 1;
		}
		else if (b1.equals("-")) {
			result = 1;
		}
		else {
			result = -1;
		}
		
		/*if (result < 0) {
			log.debug(start1 + " < " + start2);
		}
		else if (result > 0) {
			log.debug(start1 + " > " + start2);
		}
		else {
			log.debug(start1 + " = " + start2);
		}*/
		return result;
	}

	/**
	 * location type
	 * 
	 * @return location type
	 */
	public VariationLocationType getType() {
		return type;
	}

	/**
	 * start position
	 * 
	 * @return start position
	 */
	public String getStart() {
		return start;
	}

	/**
	 * stop position
	 * 
	 * @return stop position
	 */
	public String getStop() {
		return stop;
	}

	/**
	 * variation location type G: locate by genomic sequence C: locate by coding
	 * sequence
	 * 
	 * @author Wang Lei
	 */
	public enum VariationLocationType {
		G, C;

		public static VariationLocationType parseVariationLocationType(
				String variationLocationTypeString) {
			if (variationLocationTypeString.equals("c")) {
				return C;
			} else if (variationLocationTypeString.equals("g")) {
				return G;
			} else {
				throw new IllegalArgumentException(
						"Not a llegal variation location type string.");
			}
		}

		/**
		 * variation location type string
		 * 
		 * @return variation location type string
		 */
		public String toString() {
			switch (this) {
			case G:
				return "g";
			case C:
				return "c";
			default:
				return null;
			}
		}
	}

}
